/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.javlov.vf.fa;

import java.util.Random;

import net.javlov.Option;
import net.javlov.State;
import net.javlov.StateOptionMapper;
import net.javlov.StateOptionPair;


/**
 * This code is a direct port from C to Java of the code at 
 * <a target=_new href=http://rlai.cs.ualberta.ca/RLAI/RLtoolkit/tilecoding.html>the RLAI Tile Coding Page</a>.
 * 
 * It was a very quick port.  It has been tested to some degree, but as the license says, it is provided
 * with no warranty.  And the license might be a lie here, because I copied it from
 * the group's web page.
 * 
 * @author Matthijs Snel
 * @author Brian Tanner
 * @author Richard Sutton
 * @author Mark Lee
 */
public class TileCoder implements StateOptionMapper<double[],int[]> {

    final static int MAX_NUM_VARS = 300;       // Maximum number of variables in a grid-tiling      
    final static int MAX_NUM_COORDS = 100;     // Maximum number of hashing coordinates      
    final static int MaxLONGINT = 2147483647;
    static int qstate[] = new int[TileCoder.MAX_NUM_VARS];
    static int base[] = new int[MAX_NUM_VARS];
    static int coordinates[] = new int[MAX_NUM_VARS * 2 + 1];   /* one interval number per relevant dimension */

    /**
     * Use a single array to store the tiles. This will save memory but will lead to errors if
     * the tiled state is stored by some classes, since the stored state's data will change on every
     * call to {@link #getTiles(double[])}. Leaving this false will typically lead to a negligible
     * extra memory overhead and a slight speed increase.
     */
    public static boolean useSingleArray = false;
    
    int numTilings, memSize, tiles[];
    
    public TileCoder(int numTilings, int memSize) {
    	this.numTilings = numTilings;
    	this.memSize = memSize;
    	tiles = new int[numTilings];
    }
    
	@Override
	public State<int[]> map(State<double[]> s) {
		if ( !useSingleArray )
			tiles = new int[numTilings];
		return new State<int[]>(getTiles(s.getData()));
	}


	@Override
	public StateOptionPair<int[]> map(State<double[]> s, Option o) {
		throw new UnsupportedOperationException();
	}
    
    /**
     * Should this be a static method?
     * @param tiles Integer array to fill up with tile indices
     * @param tileStartOffset Where to start filling the array (used if calling multiple times with 1 array)
     * @param numTilings   Number of tilings (number of array spots to fill)
     * @param memSize   Maximum number for each array index
     * @param state       The array of double variables
     * @param ints          The array of int variables
     */
    public synchronized int[] getTiles(
            double[] state) //state to be tiled
    {
    	int stateDim = state.length;
        int num_coordinates = stateDim + 1;

        /* quantize state to integers (henceforth, tile widths == numTilings) */
        for (int i = 0; i < stateDim; i++) {
            qstate[i] = (int) (state[i] * numTilings); //This used to be math.floor be we can just cast
            base[i] = 0;
        }

        int i = 0;
        /*compute the tile numbers */
        for (int j = 0; j < numTilings; j++) {

            /* loop over each relevant dimension */
            for (i = 0; i < stateDim; i++) {

                /* find coordinates of activated tile in tiling space */
                if (qstate[i] >= base[i]) {
                    coordinates[i] = qstate[i] - ((qstate[i] - base[i]) % numTilings);
                } else {
                    coordinates[i] = qstate[i] + 1 + ((base[i] - qstate[i] - 1) % numTilings) - numTilings;
                }

                /* compute displacement of next tiling in quantized space */
                base[i] += 1 + (2 * i);
            }
            /* add additional indices for tiling and hashing_set so they hash differently */
            coordinates[i] = j;
            tiles[j] = (int) hash_UNH(coordinates, num_coordinates, memSize, 449);
        }
        return tiles;
    }

    /* hash_UNH
    Takes an array of integers and returns the corresponding tile after hashing 
     */
    static final int RNDSEQNUMBER = 16384;
    static Random theRand = new Random();
    static int rndseq[] = new int[RNDSEQNUMBER];
    static boolean first_call = true;

    long hash_UNH(int ints[], int num_ints, long m, int increment) {
        int i, k;
        long index = 0;
        long sum = 0;

        /* if first call to hashing, initialize table of random numbers */
        if (first_call) {
            for (k = 0; k < RNDSEQNUMBER; k++) {
                rndseq[k] = 0;
                for (i = 0; i < 4/*int(sizeof(int))*/; ++i) {
                    rndseq[k] = (rndseq[k] << 8) | (theRand.nextInt() & 0xff);
                }//do these need to change?
            }
            first_call = false;
        }

        for (i = 0; i < num_ints; i++) {
            /* add random table offset for this dimension and wrap around */
            index = ints[i];
            index += (increment * i);
            /* index %= RNDSEQNUMBER; */
            index = index & (RNDSEQNUMBER - 1);
            while (index < 0) {
                index += RNDSEQNUMBER;
            }

            /* add selected random number to sum */
//			System.out.println("Sum ("+sum+") += (long)rndseq["+(int)index+"] which is "+ (long)rndseq[(int)index]);
            sum += (long) rndseq[(int) index];
        }
        index = (int) (sum % m);
        while (index < 0) {
            index += m;
        }

        return (index);
    }





}
